﻿using Microsoft.EntityFrameworkCore.Query;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.Linq;
using Xms.Abp.Linq;
using Xms.Abp.ObjectMapping;
using Ef = Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions;

namespace Xms.Abp.Storage;

[Volo.Abp.DependencyInjection.Dependency(ReplaceServices = true)]
[ExposeServices(typeof(IQueryableExecuterProvider), typeof(IAsyncQueryableProvider))]
public class XmsStorageQueryableExecuterProvider : EfCoreAsyncQueryableProvider, IQueryableExecuterProvider
{
    protected Lazy<IXmsObjectMapper> ObjectMapper { get; }

    public XmsStorageQueryableExecuterProvider(Lazy<IXmsObjectMapper> objectMapper)
    {
        ObjectMapper = objectMapper;
    }

    public IQueryable<T> AsNoTracking<T>(IQueryable<T> queryable) where T : class
    {
        return Ef.AsNoTracking(queryable);
    }

    public IQueryable<T> AsTracking<T>(IQueryable<T> queryable) where T : class
    {
        return Ef.AsTracking(queryable);
    }

    public IQueryable<T> IgnoreAutoIncludes<T>(IQueryable<T> queryable) where T : class
    {
        return Ef.IgnoreAutoIncludes(queryable);
    }

    public IQueryable<T> IgnoreQueryFilters<T>(IQueryable<T> queryable) where T : class
    {
        return Ef.IgnoreQueryFilters(queryable);
    }

    public IQueryable<T> Include<T, TProperty>(IQueryable<T> queryable, Expression<Func<T, TProperty>> navigationPropertyPath) where T : class
    {
        return Ef.Include(queryable, navigationPropertyPath);
    }

    public IQueryable<T> Include<T>(IQueryable<T> queryable, string navigationPropertyPath) where T : class
    {
        return Ef.Include(queryable, navigationPropertyPath);
    }

    public IQueryable<T> ThenInclude<T, TPreviousProperty, TProperty>(IQueryable<T> queryable, Expression<Func<TPreviousProperty, TProperty>> navigationPropertyPath) where T : class
    {
        if (queryable is IIncludableQueryable<T, TPreviousProperty> includeableQueryable)
        {
            return Ef.ThenInclude(includeableQueryable, navigationPropertyPath);
        }
        if (queryable is IIncludableQueryable<T, IEnumerable<TPreviousProperty>> includeablesQueryable)
        {
            return Ef.ThenInclude(includeablesQueryable, navigationPropertyPath);
        }
        return queryable;
    }

    public IQueryable<T> TagWith<T>(IQueryable<T> queryable, string tag) where T : class
    {
        return Ef.TagWith(queryable, tag);
    }

    public IQueryable<T> TagWithCallSite<T>(IQueryable<T> queryable, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = 0) where T : class
    {
        return Ef.TagWithCallSite(queryable, filePath, lineNumber);
    }

    public string ToQueryString<T>(IQueryable<T> queryable) where T : class
    {
        return Ef.ToQueryString(queryable);
    }

    public string ToString<T>(IQueryable<T> queryable) where T : class
    {
        return Ef.ToQueryString(queryable);
    }

    public IQueryable<TResult> ProjectTo<T, TResult>(IQueryable<T> queryable)
        where T : class
        where TResult : class, new()
    {
        return ObjectMapper.Value.ProjectTo<T, TResult>(queryable);
    }
}
